#include "OpenCL.h"
#include <iostream>

cl::Platform platform;
std::vector<cl::Device> devices;
std::vector<OpenCLWork*> workers;
std::vector<OpenCLWork*> workingWorker;
unsigned int iter = 0;

struct MineException: std::exception
{
	enum Error
	{
		EXCEPTION_NO_SUCH_PLATFROM,
		EXCEPTION_NO_SUCH_Device
	};

	inline MineException(Error code) : error(code) {}

	const Error error;
};

OpenCL::OpenCL(byte platformID)
{

	try
	{
		std::vector<cl::Platform> all_platforms;
		cl::Platform::get(&all_platforms);
		if (all_platforms.size() <= platformID)
			throw MineException(MineException::EXCEPTION_NO_SUCH_PLATFROM);
		platform = all_platforms[platformID];
	}
	catch (MineException& exc)
	{
		std::cout << "no such Platform! Aborting";
		exit(1);
	}
	
}

void OpenCL::waitForWorker()
{
	while( workingWorker.size() > 0 )
	{
		workingWorker[workingWorker.size()-1]->finish();
		workingWorker.pop_back();
	}
}

void OpenCL::setWork(OpenCLWork* work)
{
	for (int i = 0; i < devices.size(); i++)
	{
		OpenCLWork* temp = work->copy(devices[i]);
		workers.push_back(temp);
	}
}
std::vector<OpenCLWork*> OpenCL::getWorker()
{
 	return workers;
}
void OpenCL::next()
{
	for (int i = 0; i < devices.size(); i++)
	{
		workingWorker.push_back(workers[i]);
		workers[i]->startWork(i, (int) devices.size());

	}
}



std::vector<int> OpenCL::getMaxComputeUnits()
{
	std::vector<int> computeUnits;
	computeUnits.resize(devices.size());
	byte i = 0;
	for each(cl::Device device in devices)
	{
		computeUnits[i] = device.getInfo<CL_DEVICE_MAX_COMPUTE_UNITS>();
		i++;

	}

	return computeUnits;
}
std::vector<int> OpenCL::getMaxWorkGroupSizes()
{
	std::vector<int> computeUnits;
	computeUnits.resize(devices.size());
	byte i = 0;
	for each(cl::Device device in devices)
	{
		computeUnits[i] = (int)device.getInfo<CL_DEVICE_MAX_WORK_GROUP_SIZE>();
		i++;
	}

	return computeUnits;
}

cl::Device OpenCL::getStandartDevice()
{
	if (devices.size() > 0)
		return devices[0];
	return NULL;
};
void OpenCL::selectDevices(std::vector<byte> deviceIDs)
{
	std::vector<cl::Device> all_devices;
	platform.getDevices(CL_DEVICE_TYPE_ALL, &all_devices);
	byte count = 0;
	devices.resize(all_devices.size());
	for each (byte var in deviceIDs)
	{
		try
		{
			if (all_devices.size() <= var)
				throw MineException(MineException::EXCEPTION_NO_SUCH_Device);
			devices[count] = all_devices[var];
			count++;

		}
		catch (MineException& exc)
		{
			std::cout << "no such Device! ID:" << (int)var << std::endl;
		}

	}
	devices.resize(count);
	if (count == 0)
	{
		std::cout << "no Devicess selected" << std::endl;
		exit(-1);
	}
}
void OpenCL::printDevices(cl::Platform localPaltform)
{
	std::vector<cl::Device> all_devices;
	cl::Platform default_platform = localPaltform;
	default_platform.getDevices(CL_DEVICE_TYPE_ALL, &all_devices);

	for (byte y = 0; y < all_devices.size(); y++)
	{
		cl_device_type dtype = all_devices[y].getInfo<CL_DEVICE_TYPE>();
		std::cout << "\t[" << (int) y << "]" << " Device Type: ";
		switch (dtype)
		{
		case CL_DEVICE_TYPE_ACCELERATOR:
			std::cout << "ACCELERATOR" << std::endl;
			break;
		case CL_DEVICE_TYPE_CPU:
			std::cout << "CPU" << std::endl;
			break;
		case CL_DEVICE_TYPE_DEFAULT:
			std::cout << "DEFAULT" << std::endl;
			break;
		case CL_DEVICE_TYPE_GPU:
			std::cout << "GPU" << std::endl;
			break;
		}
	}
}
void OpenCL::printPlatforms()
{
	std::vector<cl::Platform> all_platforms;
	cl::Platform::get(&all_platforms);
	if (all_platforms.size() == 0)
	{
		std::cout << " No platforms found. Check OpenCL installation!\n";
		exit(1);
	}
	for (byte i = 0; i < all_platforms.size(); i++)
	{
		std::cout << "[" << (int)i << "]" << "Platform Profile: " << all_platforms[i].getInfo<CL_PLATFORM_PROFILE>() << std::endl;
		std::cout << "[" << (int)i << "]" << "Platform Version: " << all_platforms[i].getInfo<CL_PLATFORM_VERSION>() << std::endl;
		std::cout << "[" << (int)i << "]" << "Platform Name: " << all_platforms[i].getInfo<CL_PLATFORM_NAME>() << std::endl;
		std::cout << "[" << (int)i << "]" << "Platform Vendor: " << all_platforms[i].getInfo<CL_PLATFORM_VENDOR>() << std::endl;
		
		printDevices(all_platforms[i]);

	}
}
void OpenCL::printDevices()
{
	printDevices(platform);
}

OpenCL::~OpenCL()
{
}
